package liquidfs.ui.vfs;

import java.util.logging.*;

import utility.*;

import net.decasdev.dokan.*;
import liquidfs.*;

public class WindowsVirtualFileSystem implements VirtualFileSystem {
   
	private int threadCount = 1;
	private Application app;
	private String mountPoint = null;
	
	private Logger log = Logger.getLogger("liquidfs.ui.vfs");
	
	private WindowsVirtualFileSystemOperations ops;
	
	public WindowsVirtualFileSystem (Application app) {
		
		// Loads DLLs
		try {
			System.loadLibrary("dokan_ia32");	// TODO: Build a 64-bit version of the dll, so that I can run this on a 64-bit JVM
			System.loadLibrary("JDokan");
		}
		catch (Throwable e) {
			throw new UserUncheckedException("Unable to start virtual file system.\n" + e.getMessage());
		}
		
		this.app = app;
		this.ops = new WindowsVirtualFileSystemOperations (app, this);
	}
	
	public int getThreadCount() {
		return threadCount;
	}

	public void setThreadCount(int threadCount) {
		this.threadCount = threadCount;
	}
	
	public boolean isMounted () {
		return this.mountPoint != null;
	}

	public String getActiveMountPoint() {
		return this.mountPoint;
	}
	
	public void mount () throws UserCheckedException {
		
		this.mountPoint = this.app.getConfiguration().getMountPoint();
		
		DokanOptions options = new DokanOptions();
		options.mountPoint = this.mountPoint;
		options.threadCount = this.threadCount;
		//options.optionsMode |= DokanOption.combine(DokanOption.DEBUG, DokanOption.STDERR);
		
		log.info("Mounting virtual file system on " + this.mountPoint);
		Dokan.removeMountPoint(this.mountPoint);
		int result = Dokan.mount(options, ops);
		
		if (result != Dokan.DOKAN_SUCCESS) {
		   
		   switch (result)
		   {
		      case Dokan.DOKAN_ERROR:
		         throw new UserCheckedException("Generic error mounting on " + this.mountPoint);
		      case Dokan.DOKAN_DRIVE_LETTER_ERROR:
               throw new UserCheckedException("The drive letter " + this.mountPoint + " is invalid.");
		      case Dokan.DOKAN_DRIVER_INSTALL_ERROR:
		         throw new UserCheckedException("The Dokan driver was not correctly installed.");
		      case Dokan.DOKAN_START_ERROR:
		         throw new UserCheckedException("The Dokan driver encountered a problem starting.");
		      case Dokan.DOKAN_MOUNT_ERROR:
               throw new UserCheckedException("Unable to assign the mount point " + this.mountPoint + "; it may already be in use.");
		      case -6: // DOKAN_MOUNT_POINT_ERROR (not defined in the lib for some reason...)
		         throw new UserCheckedException("The mount point " + this.mountPoint + " is invalid.");
		   }
		   
		}
	}
	
	public void unmount () throws UserCheckedException {
		
		if (this.mountPoint == null)
			throw new RuntimeException ("Attempted to unmount an unmounted file system.");
		
		log.info("Unmounting virtual file system on " + this.mountPoint);
		boolean success = Dokan.removeMountPoint(this.mountPoint);
		
		if (!success) {
		   throw new UserCheckedException("Unable to unmount " + this.mountPoint);
		}
	}

   @Override
   public void run() {
      try {
         this.mount();
      } catch (UserCheckedException e) {
         log.severe(e.getLocalizedMessage());
      }
   }
   
   private enum DokanOption {
      DEBUG       (1),  // ouput debug message
      STDERR      (2),  // ouput debug message to stderr
      ALT_STREAM  (4),  // use alternate stream
      KEEP_ALIVE  (8),  // use auto unmount
      NETWORK     (16), // use network drive,
                        // you need to install Dokan network provider.
      REMOVABLE   (32); // use removable drive
      
      private int constant;
      
      DokanOption (int constant) {
         this.constant = constant;
      }
      
      public int getConstant () {
         return this.constant;
      }
      
      public static int combine (DokanOption... options) {
         int combination = 0;
         
         for (DokanOption option : options) {
            combination |= option.getConstant();
         }
         
         return combination;
      }
   }
}
